# -*- coding: utf-8 -*-
"""
FFmpeg服务 - 跨平台统一封装
"""

import asyncio
import json
import logging
import os
import subprocess
import sys
import psutil
import time
from typing import Dict, Any, List, Tuple, Optional
import platform

from app.core.config import settings

logger = logging.getLogger(__name__)


class FFmpegService:
    """跨平台FFmpeg服务封装"""
    
    def __init__(self):
        # 直接从settings读取环境变量配置
        self.ffmpeg_path = settings.FFMPEG_PATH
        self.ffprobe_path = settings.FFPROBE_PATH
        
        # 配置事件循环
        self._configure_event_loop()
        
        # 存储任务ID和对应的进程信息
        self.tasks = {}
        # 任务ID和进程ID的映射
        self.task_pid_map = {}
        # 存储任务的退出代码
        self.task_exit_codes = {}
        # 存储任务的错误信息
        self.task_errors = {}
        # 存储任务的标准输出
        self.task_stdout = {}
        # 存储任务的标准错误
        self.task_stderr = {}
        # 存储任务的启动时间
        self.task_start_times = {}
    
    def _register_task(self, task_id: str, process=None, pid: int = None):
        """注册任务和进程信息"""
        if task_id:
            self.tasks[task_id] = {
                'process': process,
                'pid': pid,
                'start_time': time.time()
            }
            if pid:
                self.task_pid_map[pid] = task_id
    
    def _unregister_task(self, task_id: str):
        """注销任务信息"""
        if task_id in self.tasks:
            pid = self.tasks[task_id].get('pid')
            if pid and pid in self.task_pid_map:
                del self.task_pid_map[pid]
            del self.tasks[task_id]
            if task_id in self.task_exit_codes:
                del self.task_exit_codes[task_id]
            if task_id in self.task_errors:
                del self.task_errors[task_id]
            if task_id in self.task_stdout:
                del self.task_stdout[task_id]
            if task_id in self.task_stderr:
                del self.task_stderr[task_id]
    
    def _configure_event_loop(self):
        """配置事件循环策略"""
        try:
            if platform.system().lower() == "windows":
                import asyncio
                if hasattr(asyncio, "WindowsSelectorEventLoopPolicy"):
                    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
                    logger.info("已配置Windows事件循环策略")
        except Exception as e:
            logger.error(f"配置事件循环失败: {str(e)}")

    async def _collect_process_output(self, task_id: str, process):
        """
        异步收集进程的输出流并更新任务状态
        
        Args:
            task_id: 任务ID
            process: 异步进程对象
        """
        try:
            # 异步读取stdout和stderr
            stdout, stderr = await process.communicate()
            
            # 转换为字符串
            stdout_str = stdout.decode('utf-8', errors='ignore') if stdout else ''
            stderr_str = stderr.decode('utf-8', errors='ignore') if stderr else ''
            
            # 更新任务状态
            self.task_stdout[task_id] = stdout_str
            self.task_stderr[task_id] = stderr_str
            self.task_exit_codes[task_id] = process.returncode
            
            if process.returncode != 0:
                self.task_errors[task_id] = stderr_str
                logger.error(f"FFmpeg任务 {task_id} 执行失败，返回码: {process.returncode}")
            else:
                logger.info(f"FFmpeg任务 {task_id} 执行成功")
                
        except Exception as e:
            logger.error(f"收集进程输出异常: {str(e)}")
            # 更新错误信息
            if task_id in self.tasks:
                self.task_exit_codes[task_id] = -1
                self.task_errors[task_id] = str(e)
        
    def _configure_event_loop(self):
        """配置事件循环以提高性能"""
        try:
            # 在Windows平台上使用WindowsSelectorEventLoopPolicy以提高性能
            if sys.platform == 'win32':
                asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
                logger.info("已配置Windows事件循环策略")
        except Exception as e:
            logger.warning(f"配置事件循环策略失败: {e}")
    
    async def _execute_ffmpeg_command(self, cmd: List[str], timeout: int = 300, task_id: str = None) -> Tuple[int, str, str, Optional[int]]:
        """
        跨平台异步执行FFmpeg命令
        
        Args:
            cmd: 命令列表
            timeout: 超时时间(秒) - 用于检测输入源是否可访问
            task_id: 可选的任务ID，用于跟踪进程
            
        Returns:
            (返回码, stdout, stderr, 进程ID)
            - 返回码为0表示进程成功启动
            - 返回码为-1表示进程启动失败或超时
            - 进程ID为None表示启动失败
        """
        process_pid = None
        
        try:
            # logger.info(f"执行FFmpeg命令: {' '.join(cmd)}")
            
            # Windows兼容性处理
            if sys.platform.startswith('win'):
                # Windows使用线程池执行同步子进程，避免阻塞事件循环
                import concurrent.futures
                loop = asyncio.get_event_loop()
                
                def run_subprocess():
                    nonlocal process_pid
                    try:
                        # 使用Popen启动进程
                        process = subprocess.Popen(
                            cmd,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            text=True,
                            encoding='utf-8',
                            errors='ignore'
                        )
                        
                        # 记录进程ID
                        process_pid = process.pid
                        if task_id:
                            loop.call_soon_threadsafe(self._register_task, task_id, None, process_pid)
                        
                        # 对于转码任务，我们只需要确认进程启动成功，不等待完成
                        # 超时检查主要用于确认输入源可访问性
                        # 这里只等待一小段时间来检查是否有立即失败的情况
                        # 真正的任务完成监控由_process_task中的轮询完成
                        try:
                            # 只等待一小段时间来检查进程是否立即失败
                            process.wait(timeout=1)
                            # 如果进程在1秒内退出，说明启动失败
                            stdout, stderr = process.communicate()
                            return -1, stdout, stderr, process_pid
                        except subprocess.TimeoutExpired:
                            # 进程正常运行中
                            return 0, "", "", process_pid
                    except Exception as e:
                        return -1, "", str(e), None
                
                # 在线程池中执行阻塞操作
                with concurrent.futures.ThreadPoolExecutor() as executor:
                    return_code, stdout, stderr, process_pid = await loop.run_in_executor(
                        executor, run_subprocess
                    )
                    
            else:
                # Unix系统使用原生异步子进程
                try:
                    process = await asyncio.create_subprocess_exec(
                        *cmd,
                        stdout=asyncio.subprocess.PIPE,
                        stderr=asyncio.subprocess.PIPE
                    )
                    # 对于Unix系统，我们只检查进程是否启动成功，不等待完成
                    # 设置返回码为0表示成功启动
                    return_code = 0
                    # 注意：这里不设置stdout和stderr为空字符串
                    # 因为我们需要保留process对象来收集后续的输出
                    # 记录进程ID
                    process_pid = process.pid
                    if task_id:
                        self._register_task(task_id, process=process, pid=process_pid)
                        # 启动一个异步任务来收集进程的输出
                        asyncio.create_task(self._collect_process_output(task_id, process))
                except Exception as e:
                    logger.error(f"执行FFmpeg命令异常: {str(e)}")
                    return -1, "", str(e), None
            
            # 处理任务启动信息
            if task_id:
                # 仅设置返回码，不覆盖stdout和stderr，因为在Unix系统中这些会由_collect_process_output异步收集
                # 对于Windows系统，stdout和stderr已经是可用的
                self.task_exit_codes[task_id] = return_code
                
                # 对于Windows系统，设置完整的输出信息
                if sys.platform.startswith('win'):
                    self.task_errors[task_id] = stderr
                    self.task_stdout[task_id] = stdout
                    self.task_stderr[task_id] = stderr
                else:
                    # 对于Unix系统，设置初始空值，后续会被_collect_process_output覆盖
                    if task_id not in self.task_stdout:
                        self.task_stdout[task_id] = ""
                    if task_id not in self.task_stderr:
                        self.task_stderr[task_id] = ""
                    if return_code != 0 and task_id not in self.task_errors:
                        self.task_errors[task_id] = stderr or "进程启动失败"

                if return_code == 0:
                    logger.info(f"FFmpeg进程成功启动，任务ID: {task_id}, PID: {process_pid}")
                else:
                    logger.error(f"FFmpeg进程启动失败，任务ID: {task_id}")
                    
            
            return return_code, stdout, stderr, process_pid
            
        except Exception as e:
            logger.error(f"执行FFmpeg命令异常: {str(e)}")
            # 如果有任务ID，记录错误
            if task_id:
                self.task_exit_codes[task_id] = -1
                self.task_errors[task_id] = str(e)
            return -1, "", str(e), process_pid
    
    async def validate_stream(self, stream_url: str) -> Dict[str, Any]:
        """
        验证流地址有效性
        
        Args:
            stream_url: 流地址
            
        Returns:
            包含验证结果的字典
        """
        try:
            # print(f"开始验证流: {stream_url}")
            
            cmd = [
                self.ffprobe_path,
                '-v', 'quiet',
                '-print_format', 'json',
                '-show_streams',
                '-show_format',
                '-timeout', '10000000',
                stream_url
            ]
            
            return_code, stdout, stderr, _ = await self._execute_ffmpeg_command(cmd, timeout=30)
            
            if return_code != 0:
                return {
                    "valid": False,
                    "error": f"FFprobe验证失败: {stderr}",
                    "return_code": return_code
                }
            
            # 解析JSON输出
            try:
                probe_data = json.loads(stdout)
            except json.JSONDecodeError as e:
                return {
                    "valid": False,
                    "error": f"JSON解析失败: {str(e)}",
                    "raw_output": stdout
                }
            
            # 检查是否有视频流
            has_video = any(
                stream.get('codec_type') == 'video'
                for stream in probe_data.get('streams', [])
            )
            
            if not has_video:
                return {
                    "valid": False,
                    "error": "未检测到视频流",
                    "probe_data": probe_data
                }
            
            # 获取流信息
            format_info = probe_data.get('format', {})
            duration = float(format_info.get('duration', 0))
            
            return {
                "valid": True,
                "duration": duration,
                "format": format_info.get('format_name', 'unknown'),
                "bitrate": int(format_info.get('bit_rate', 0)),
                "streams": len(probe_data.get('streams', [])),
                "probe_data": probe_data
            }
            
        except Exception as e:
            return {
                "valid": False,
                "error": f"验证异常: {str(e)}"
            }
    
    def stop_task(self, task_id: str) -> bool:
        """
        停止指定的FFmpeg任务进程
        
        Args:
            task_id: 任务ID
            
        Returns:
            操作是否成功
        """
        try:
            if task_id not in self.tasks:
                logger.warning(f"任务 {task_id} 不存在")
                return False
                
            task_info = self.tasks[task_id]
            pid = task_info.get('pid')
            
            if not pid:
                logger.warning(f"任务 {task_id} 没有关联的进程ID")
                return False
                
            logger.info(f"尝试停止任务 {task_id} 的进程 {pid}")
            
            try:
                # 使用psutil终止进程
                process = psutil.Process(pid)
                
                # 先尝试优雅终止
                process.terminate()
                
                # 等待进程终止，最多等待5秒
                try:
                    process.wait(timeout=5)
                    logger.info(f"任务 {task_id} 的进程 {pid} 已成功终止")
                except psutil.TimeoutExpired:
                    # 如果优雅终止失败，强制终止
                    process.kill()
                    logger.warning(f"任务 {task_id} 的进程 {pid} 已强制终止")
                
                # 记录退出状态
                self.task_exit_codes[task_id] = -2  # 表示被终止
                self.task_errors[task_id] = '任务被用户取消'
                
                # 注销任务
                self._unregister_task(task_id)
                
                return True
            except psutil.NoSuchProcess:
                logger.warning(f"任务 {task_id} 的进程 {pid} 不存在")
                # 清理任务记录
                self._unregister_task(task_id)
                return True
            except Exception as e:
                logger.error(f"停止任务 {task_id} 的进程 {pid} 失败: {str(e)}")
                return False
        except Exception as e:
            logger.error(f"停止任务 {task_id} 失败: {str(e)}")
            return False
            
    def get_process_status(self, task_id: str) -> Optional[Dict[str, Any]]:
        """
        获取FFmpeg进程状态和资源消耗信息
        
        Args:
            task_id: 任务ID
            
        Returns:
            进程状态字典，如果任务不存在则返回None
        """
        if task_id not in self.tasks:
            return None
            
        task_info = self.tasks[task_id]
        pid = task_info.get('pid')
        
        if not pid:
            # 检查是否有退出代码（可能已经完成）
            if task_id in self.task_exit_codes:
                return_code = self.task_exit_codes[task_id]
                return {
                    'status': 'completed' if return_code == 0 else 'failed',
                    'pid': pid,
                    'return_code': return_code,
                    'stdout': self.task_stdout.get(task_id, ''),
                    'stderr': self.task_stderr.get(task_id, ''),
                    'error': self.task_errors.get(task_id, '') if return_code != 0 else None
                }
            return None
            
        try:
            # 使用psutil检查进程是否存在
            process = psutil.Process(pid)
            
            # 检查进程是否正在运行
            if process.is_running():
                # 检查进程名称是否包含ffmpeg（额外验证）
                name = process.name().lower()
                if 'ffmpeg' in name or 'ffprobe' in name:
                    # 使用oneshot()方法提高性能
                    with process.oneshot():
                        try:
                            # 获取资源使用情况
                            cpu_percent = process.cpu_percent(interval=0.1)
                            # 内存使用率保留两位小数，减轻系统压力
                            memory_percent = round(process.memory_percent(), 2)
                            # 获取内存使用量（MB）
                            memory_usage_mb = process.memory_info().rss / 1024 / 1024
                            # 获取运行时间
                            runtime = time.time() - process.create_time()
                        except:
                            cpu_percent = 0
                            memory_percent = 0
                            memory_usage_mb = 0
                            runtime = 0
                        
                        return {
                            'status': 'running',
                            'pid': pid,
                            'cpu_percent': cpu_percent,
                            'memory_percent': memory_percent,
                            'memory_usage_mb': round(memory_usage_mb, 2),
                            # 只使用秒级精度，减轻系统压力
                            'create_time': int(process.create_time()),
                            'runtime_seconds': round(runtime, 2)
                        }
            
            # 进程不存在或不是ffmpeg进程
            # 检查是否有退出代码记录
            if task_id in self.task_exit_codes:
                return_code = self.task_exit_codes[task_id]
                error_info = self.task_errors.get(task_id, '') if return_code != 0 else None
                # 尝试从stderr中提取更详细的错误信息
                if not error_info and task_id in self.task_stderr and self.task_stderr[task_id]:
                    stderr_lines = self.task_stderr[task_id].split('\n')
                    error_lines = []
                    # 寻找包含错误关键词的行
                    for line in reversed(stderr_lines):
                        if line and ('error' in line.lower() or 'failed' in line.lower() or 'invalid' in line.lower()):
                            error_lines.append(line)
                        if len(error_lines) >= 5:  # 最多显示5行错误信息
                            break
                    if error_lines:
                        error_info = '\n'.join(reversed(error_lines))
                
                return {
                    'status': 'completed' if return_code == 0 else 'failed',
                    'pid': pid,
                    'return_code': return_code,
                    'stdout': self.task_stdout.get(task_id, ''),
                    'stderr': self.task_stderr.get(task_id, ''),
                    'error': error_info
                }
            
            # 进程不存在且没有退出代码记录，标记为失败
            self.task_exit_codes[task_id] = -1
            self.task_errors[task_id] = '进程意外终止'
            return {
                'status': 'failed',
                'pid': pid,
                'return_code': -1,
                'error': '进程意外终止'
            }
        except psutil.NoSuchProcess:
            # 进程不存在
            if task_id in self.task_exit_codes:
                return_code = self.task_exit_codes[task_id]
                error_info = self.task_errors.get(task_id, '') if return_code != 0 else None
                # 尝试从stderr中提取更详细的错误信息
                if not error_info and task_id in self.task_stderr and self.task_stderr[task_id]:
                    stderr_lines = self.task_stderr[task_id].split('\n')
                    error_lines = []
                    # 寻找包含错误关键词的行
                    for line in reversed(stderr_lines):
                        if line and ('error' in line.lower() or 'failed' in line.lower() or 'invalid' in line.lower()):
                            error_lines.append(line)
                        if len(error_lines) >= 5:  # 最多显示5行错误信息
                            break
                    if error_lines:
                        error_info = '\n'.join(reversed(error_lines))
                
                return {
                    'status': 'completed' if return_code == 0 else 'failed',
                    'pid': pid,
                    'return_code': return_code,
                    'stdout': self.task_stdout.get(task_id, ''),
                    'stderr': self.task_stderr.get(task_id, ''),
                    'error': error_info
                }
            
            # 进程不存在且没有退出代码记录
            self.task_exit_codes[task_id] = -1
            self.task_errors[task_id] = '进程不存在'
            return {
                'status': 'failed',
                'pid': pid,
                'return_code': -1,
                'error': '进程不存在'
            }
        except Exception as e:
            logger.error(f"获取进程状态失败: {str(e)}")
            return {
                'status': 'unknown',
                'pid': pid,
                'error': str(e)
            }
            
    async def start_task(self, command: str, task_id: str = None, timeout: int = 60) -> Dict[str, Any]:
        """
        启动FFmpeg任务
        
        Args:
            command: FFmpeg命令字符串
            task_id: 任务ID
            timeout: 超时时间(秒)，用于检测输入源是否可访问
            
        Returns:
            任务启动结果，包含success状态、任务ID、进程ID和输出信息
        """
        try:
            print(f"启动FFmpeg任务{' ' + task_id if task_id else ''}")
             # 记录启动时间
            if task_id:
                self.task_start_times[task_id] = time.time()            
            # 解析命令字符串为列表格式
            # 注意：这里需要正确处理包含引号的参数
            cmd = []
            in_quotes = False
            current_arg = ''
            
            for char in command:
                if char == '"':
                    in_quotes = not in_quotes
                elif char == ' ' and not in_quotes:
                    if current_arg:
                        cmd.append(current_arg)
                        current_arg = ''
                else:
                    current_arg += char
            
            if current_arg:
                cmd.append(current_arg)            
            
            
            # 执行命令，传入task_id以便跟踪进程 - 所有转码任务都是非阻塞模式
            return_code, stdout, stderr, pid = await self._execute_ffmpeg_command(cmd, timeout=timeout, task_id=task_id)
            
            # 记录任务状态
            result = {
                "success": 1 if return_code == 0 else 0,
                "task_id": task_id,
                "pid": pid,
                "stdout": stdout,
                "stderr": stderr,
                "return_code": return_code
            }
            
            # 如果执行失败，添加错误信息
            if return_code != 0:
                result["error"] = f"任务执行失败: {stderr}"
                result["return_code"] = return_code
            
            # 注意：这里返回整个result字典，而不仅仅是success布尔值
            # 这样调用者可以获取更多信息
            return result
                
        except Exception as e:
            error_msg = f"启动FFmpeg任务异常: {str(e)}"
            logger.error(error_msg)
            return {
                "success": 0,
                "error": error_msg,
                "task_id": task_id,
                "pid": None
            }


# 创建全局服务实例
ffmpeg_service = FFmpegService()


def get_ffmpeg_service() -> FFmpegService:
    """
    获取FFmpeg服务实例
    
    Returns:
        FFmpegService实例
    """
    return ffmpeg_service